import * as React from 'react';
import { injectable, inject, postConstruct } from 'inversify';
import { Widget } from '@phosphor/widgets';
import { Message } from '@phosphor/messaging';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import 'react-tabs/style/react-tabs.css';
import { Disable } from 'react-disable';
import URI from '@theia/core/lib/common/uri';
import { Emitter } from '@theia/core/lib/common/event';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { deepClone } from '@theia/core/lib/common/objects';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { MaybePromise } from '@theia/core/lib/common/types';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog/file-dialog-service';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import {
    AbstractDialog,
    DialogProps,
    PreferenceService,
    PreferenceScope,
    DialogError,
    ReactWidget,
} from '@theia/core/lib/browser';
import { Index } from '../common/types';
import {
    CompilerWarnings,
    CompilerWarningLiterals,
    ConfigService,
    FileSystemExt,
    Network,
    ProxySettings,
} from '../common/protocol';

export interface Settings extends Index {
    editorFontSize: number; // `editor.fontSize`
    themeId: string; // `workbench.colorTheme`
    autoSave: 'on' | 'off'; // `editor.autoSave`
    quickSuggestions: Record<'other' | 'comments' | 'strings', boolean>; // `editor.quickSuggestions`

    autoScaleInterface: boolean; // `arduino.window.autoScale`
    interfaceScale: number; // `arduino.window.zoomLevel` https://github.com/eclipse-theia/theia/issues/8751
    checkForUpdates?: boolean; // `arduino.ide.autoUpdate`
    verboseOnCompile: boolean; // `arduino.compile.verbose`
    compilerWarnings: CompilerWarnings; // `arduino.compile.warnings`
    verboseOnUpload: boolean; // `arduino.upload.verbose`
    verifyAfterUpload: boolean; // `arduino.upload.verify`
    enableLsLogs: boolean; // `arduino.language.log`
    sketchbookShowAllFiles: boolean; // `arduino.sketchbook.showAllFiles`

    sketchbookPath: string; // CLI
    additionalUrls: string[]; // CLI
    network: Network; // CLI
}
export namespace Settings {
    export function belongsToCli<K extends keyof Settings>(key: K): boolean {
        return key === 'sketchbookPath' || key === 'additionalUrls';
    }
}

@injectable()
export class SettingsService {
    @inject(FileService)
    protected readonly fileService: FileService;

    @inject(FileSystemExt)
    protected readonly fileSystemExt: FileSystemExt;

    @inject(ConfigService)
    protected readonly configService: ConfigService;

    @inject(PreferenceService)
    protected readonly preferenceService: PreferenceService;

    @inject(FrontendApplicationStateService)
    protected readonly appStateService: FrontendApplicationStateService;

    protected readonly onDidChangeEmitter = new Emitter<Readonly<Settings>>();
    readonly onDidChange = this.onDidChangeEmitter.event;

    protected ready = new Deferred<void>();
    protected _settings: Settings;

    @postConstruct()
    protected async init(): Promise<void> {
        await this.appStateService.reachedState('ready'); // Hack for https://github.com/eclipse-theia/theia/issues/8993
        const settings = await this.loadSettings();
        this._settings = deepClone(settings);
        this.ready.resolve();
    }

    protected async loadSettings(): Promise<Settings> {
        await this.preferenceService.ready;
        const [
            editorFontSize,
            themeId,
            autoSave,
            quickSuggestions,
            autoScaleInterface,
            interfaceScale,
            // checkForUpdates,
            verboseOnCompile,
            compilerWarnings,
            verboseOnUpload,
            verifyAfterUpload,
            enableLsLogs,
            sketchbookShowAllFiles,
            cliConfig,
        ] = await Promise.all([
            this.preferenceService.get<number>('editor.fontSize', 12),
            this.preferenceService.get<string>(
                'workbench.colorTheme',
                'arduino-theme'
            ),
            this.preferenceService.get<'on' | 'off'>('editor.autoSave', 'on'),
            this.preferenceService.get<Record<string, unknown>>(
                'editor.quickSuggestion',
                {
                    other: false,
                    comments: false,
                    strings: false,
                }
            ),
            this.preferenceService.get<boolean>(
                'arduino.window.autoScale',
                true
            ),
            this.preferenceService.get<number>('arduino.window.zoomLevel', 0),
            // this.preferenceService.get<string>('arduino.ide.autoUpdate', true),
            this.preferenceService.get<boolean>(
                'arduino.compile.verbose',
                true
            ),
            this.preferenceService.get<any>('arduino.compile.warnings', 'None'),
            this.preferenceService.get<boolean>('arduino.upload.verbose', true),
            this.preferenceService.get<boolean>('arduino.upload.verify', true),
            this.preferenceService.get<boolean>('arduino.language.log', true),
            this.preferenceService.get<boolean>(
                'arduino.sketchbook.showAllFiles',
                false
            ),
            this.configService.getConfiguration(),
        ]);
        const { additionalUrls, sketchDirUri, network } = cliConfig;
        const sketchbookPath = await this.fileService.fsPath(
            new URI(sketchDirUri)
        );
        return {
            editorFontSize,
            themeId,
            autoSave,
            quickSuggestions,
            autoScaleInterface,
            interfaceScale,
            // checkForUpdates,
            verboseOnCompile,
            compilerWarnings,
            verboseOnUpload,
            verifyAfterUpload,
            enableLsLogs,
            sketchbookShowAllFiles,
            additionalUrls,
            sketchbookPath,
            network,
        };
    }

    async settings(): Promise<Settings> {
        await this.ready.promise;
        return this._settings;
    }

    async update(settings: Settings, fireDidChange = false): Promise<void> {
        await this.ready.promise;
        for (const key of Object.keys(settings)) {
            this._settings[key] = settings[key];
        }
        if (fireDidChange) {
            this.onDidChangeEmitter.fire(this._settings);
        }
    }

    async reset(): Promise<void> {
        const settings = await this.loadSettings();
        return this.update(settings, true);
    }

    async validate(
        settings: MaybePromise<Settings> = this.settings()
    ): Promise<string | true> {
        try {
            const { sketchbookPath, editorFontSize, themeId } = await settings;
            const sketchbookDir = await this.fileSystemExt.getUri(
                sketchbookPath
            );
            if (!(await this.fileService.exists(new URI(sketchbookDir)))) {
                return `Invalid sketchbook location: ${sketchbookPath}`;
            }
            if (editorFontSize <= 0) {
                return 'Invalid editor font size. It must be a positive integer.';
            }
            if (
                !ThemeService.get()
                    .getThemes()
                    .find(({ id }) => id === themeId)
            ) {
                return 'Invalid theme.';
            }
            return true;
        } catch (err) {
            if (err instanceof Error) {
                return err.message;
            }
            return String(err);
        }
    }

    async save(): Promise<string | true> {
        await this.ready.promise;
        const {
            editorFontSize,
            themeId,
            autoSave,
            quickSuggestions,
            autoScaleInterface,
            interfaceScale,
            // checkForUpdates,
            verboseOnCompile,
            compilerWarnings,
            verboseOnUpload,
            verifyAfterUpload,
            enableLsLogs,
            sketchbookPath,
            additionalUrls,
            network,
            sketchbookShowAllFiles,
        } = this._settings;
        const [config, sketchDirUri] = await Promise.all([
            this.configService.getConfiguration(),
            this.fileSystemExt.getUri(sketchbookPath),
        ]);
        (config as any).additionalUrls = additionalUrls;
        (config as any).sketchDirUri = sketchDirUri;
        (config as any).network = network;

        await Promise.all([
            this.preferenceService.set(
                'editor.fontSize',
                editorFontSize,
                PreferenceScope.User
            ),
            this.preferenceService.set(
                'workbench.colorTheme',
                themeId,
                PreferenceScope.User
            ),
            this.preferenceService.set(
                'editor.autoSave',
                autoSave,
                PreferenceScope.User
            ),
            this.preferenceService.set(
                'editor.quickSuggestions',
                quickSuggestions,
                PreferenceScope.User
            ),
            this.preferenceService.set(
                'arduino.window.autoScale',
                autoScaleInterface,
                PreferenceScope.User
            ),
            this.preferenceService.set(
                'arduino.window.zoomLevel',
                interfaceScale,
                PreferenceScope.User
            ),
            // this.preferenceService.set('arduino.ide.autoUpdate', checkForUpdates, PreferenceScope.User),
            this.preferenceService.set(
                'arduino.compile.verbose',
                verboseOnCompile,
                PreferenceScope.User
            ),
            this.preferenceService.set(
                'arduino.compile.warnings',
                compilerWarnings,
                PreferenceScope.User
            ),
            this.preferenceService.set(
                'arduino.upload.verbose',
                verboseOnUpload,
                PreferenceScope.User
            ),
            this.preferenceService.set(
                'arduino.upload.verify',
                verifyAfterUpload,
                PreferenceScope.User
            ),
            this.preferenceService.set(
                'arduino.language.log',
                enableLsLogs,
                PreferenceScope.User
            ),
            this.preferenceService.set(
                'arduino.sketchbook.showAllFiles',
                sketchbookShowAllFiles,
                PreferenceScope.User
            ),
            this.configService.setConfiguration(config),
        ]);
        this.onDidChangeEmitter.fire(this._settings);
        return true;
    }
}

export class SettingsComponent extends React.Component<
    SettingsComponent.Props,
    SettingsComponent.State
> {
    readonly toDispose = new DisposableCollection();

    constructor(props: SettingsComponent.Props) {
        super(props);
    }

    componentDidUpdate(
        _: SettingsComponent.Props,
        prevState: SettingsComponent.State
    ): void {
        if (
            this.state &&
            prevState &&
            JSON.stringify(this.state) !== JSON.stringify(prevState)
        ) {
            this.props.settingsService.update(this.state, true);
        }
    }

    componentDidMount(): void {
        this.props.settingsService
            .settings()
            .then((settings) => this.setState(settings));
        this.toDispose.push(
            this.props.settingsService.onDidChange((settings) =>
                this.setState(settings)
            )
        );
    }

    componentWillUnmount(): void {
        this.toDispose.dispose();
    }

    render(): React.ReactNode {
        if (!this.state) {
            return <div />;
        }
        return (
            <Tabs>
                <TabList>
                    <Tab>Settings</Tab>
                    <Tab>Network</Tab>
                </TabList>
                <TabPanel>{this.renderSettings()}</TabPanel>
                <TabPanel>{this.renderNetwork()}</TabPanel>
            </Tabs>
        );
    }

    protected renderSettings(): React.ReactNode {
        return (
            <div className="content noselect">
                Sketchbook location:
                <div className="flex-line">
                    <input
                        className="theia-input stretch"
                        type="text"
                        value={this.state.sketchbookPath}
                        onChange={this.sketchpathDidChange}
                    />
                    <button
                        className="theia-button shrink"
                        onClick={this.browseSketchbookDidClick}
                    >
                        Browse
                    </button>
                </div>
                <label className="flex-line">
                    <input
                        type="checkbox"
                        checked={this.state.sketchbookShowAllFiles === true}
                        onChange={this.sketchbookShowAllFilesDidChange}
                    />
                    Show files inside Sketches
                </label>
                <div className="flex-line">
                    <div className="column">
                        <div className="flex-line">Editor font size:</div>
                        <div className="flex-line">Interface scale:</div>
                        <div className="flex-line">Theme:</div>
                        <div className="flex-line">
                            Show verbose output during:
                        </div>
                        <div className="flex-line">Compiler warnings:</div>
                    </div>
                    <div className="column">
                        <div className="flex-line">
                            <input
                                className="theia-input small"
                                type="number"
                                step={1}
                                pattern="[0-9]+"
                                onKeyDown={this.numbersOnlyKeyDown}
                                value={this.state.editorFontSize}
                                onChange={this.editorFontSizeDidChange}
                            />
                        </div>
                        <div className="flex-line">
                            <label className="flex-line">
                                <input
                                    type="checkbox"
                                    checked={this.state.autoScaleInterface}
                                    onChange={this.autoScaleInterfaceDidChange}
                                />
                                Automatic
                            </label>
                            <input
                                className="theia-input small with-margin"
                                type="number"
                                step={20}
                                pattern="[0-9]+"
                                onKeyDown={this.noopKeyDown}
                                value={100 + this.state.interfaceScale * 20}
                                onChange={this.interfaceScaleDidChange}
                            />
                            %
                        </div>
                        <div className="flex-line">
                            <select
                                className="theia-select"
                                value={
                                    ThemeService.get()
                                        .getThemes()
                                        .find(
                                            ({ id }) =>
                                                id === this.state.themeId
                                        )?.label || 'Unknown'
                                }
                                onChange={this.themeDidChange}
                            >
                                {ThemeService.get()
                                    .getThemes()
                                    .map(({ id, label }) => (
                                        <option key={id} value={label}>
                                            {label}
                                        </option>
                                    ))}
                            </select>
                        </div>
                        <div className="flex-line">
                            <label className="flex-line">
                                <input
                                    type="checkbox"
                                    checked={this.state.verboseOnCompile}
                                    onChange={this.verboseOnCompileDidChange}
                                />
                                compile
                            </label>
                            <label className="flex-line">
                                <input
                                    type="checkbox"
                                    checked={this.state.verboseOnUpload}
                                    onChange={this.verboseOnUploadDidChange}
                                />
                                upload
                            </label>
                        </div>
                        <div className="flex-line">
                            <select
                                className="theia-select"
                                value={this.state.compilerWarnings}
                                onChange={this.compilerWarningsDidChange}
                            >
                                {CompilerWarningLiterals.map((value) => (
                                    <option key={value} value={value}>
                                        {value}
                                    </option>
                                ))}
                            </select>
                        </div>
                    </div>
                </div>
                <label className="flex-line">
                    <input
                        type="checkbox"
                        checked={this.state.verifyAfterUpload}
                        onChange={this.verifyAfterUploadDidChange}
                    />
                    Verify code after upload
                </label>
                <label className="flex-line">
                    <input
                        type="checkbox"
                        checked={this.state.checkForUpdates}
                        onChange={this.checkForUpdatesDidChange}
                        disabled={true}
                    />
                    Check for updates on startup
                </label>
                <label className="flex-line">
                    <input
                        type="checkbox"
                        checked={this.state.autoSave === 'on'}
                        onChange={this.autoSaveDidChange}
                    />
                    Auto save
                </label>
                <label className="flex-line">
                    <input
                        type="checkbox"
                        checked={this.state.quickSuggestions.other === true}
                        onChange={this.quickSuggestionsOtherDidChange}
                    />
                    Editor Quick Suggestions
                </label>
                <label className="flex-line">
                    <input
                        type="checkbox"
                        checked={this.state.enableLsLogs}
                        onChange={this.enableLsLogsDidChange}
                    />
                    Enable language server logging
                </label>
                <div className="flex-line">
                    Additional boards manager URLs:
                    <input
                        className="theia-input stretch with-margin"
                        type="text"
                        value={this.state.additionalUrls.join(',')}
                        onChange={this.additionalUrlsDidChange}
                    />
                    <i
                        className="fa fa-window-restore theia-button shrink"
                        onClick={this.editAdditionalUrlDidClick}
                    />
                </div>
            </div>
        );
    }

    protected renderNetwork(): React.ReactNode {
        return (
            <div className="content noselect">
                <form>
                    <label className="flex-line">
                        <input
                            type="radio"
                            checked={this.state.network === 'none'}
                            onChange={this.noProxyDidChange}
                        />
                        No proxy
                    </label>
                    <label className="flex-line">
                        <input
                            type="radio"
                            checked={this.state.network !== 'none'}
                            onChange={this.manualProxyDidChange}
                        />
                        Manual proxy configuration
                    </label>
                </form>
                {this.renderProxySettings()}
            </div>
        );
    }

    protected renderProxySettings(): React.ReactNode {
        const disabled = this.state.network === 'none';
        return (
            <Disable disabled={disabled}>
                <div className="proxy-settings" aria-disabled={disabled}>
                    <form className="flex-line">
                        <input
                            type="radio"
                            checked={
                                this.state.network === 'none'
                                    ? true
                                    : this.state.network.protocol === 'http'
                            }
                            onChange={this.httpProtocolDidChange}
                        />
                        HTTP
                        <label className="flex-line">
                            <input
                                type="radio"
                                checked={
                                    this.state.network === 'none'
                                        ? false
                                        : this.state.network.protocol !== 'http'
                                }
                                onChange={this.socksProtocolDidChange}
                            />
                            SOCKS
                        </label>
                    </form>
                    <div className="flex-line proxy-settings">
                        <div className="column">
                            <div className="flex-line">Host name:</div>
                            <div className="flex-line">Port number:</div>
                            <div className="flex-line">Username:</div>
                            <div className="flex-line">Password:</div>
                        </div>
                        <div className="column stretch">
                            <div className="flex-line">
                                <input
                                    className="theia-input stretch with-margin"
                                    type="text"
                                    value={
                                        this.state.network === 'none'
                                            ? ''
                                            : this.state.network.hostname
                                    }
                                    onChange={this.hostnameDidChange}
                                />
                            </div>
                            <div className="flex-line">
                                <input
                                    className="theia-input small with-margin"
                                    type="number"
                                    pattern="[0-9]"
                                    value={
                                        this.state.network === 'none'
                                            ? ''
                                            : this.state.network.port
                                    }
                                    onKeyDown={this.numbersOnlyKeyDown}
                                    onChange={this.portDidChange}
                                />
                            </div>
                            <div className="flex-line">
                                <input
                                    className="theia-input stretch with-margin"
                                    type="text"
                                    value={
                                        this.state.network === 'none'
                                            ? ''
                                            : this.state.network.username
                                    }
                                    onChange={this.usernameDidChange}
                                />
                            </div>
                            <div className="flex-line">
                                <input
                                    className="theia-input stretch with-margin"
                                    type="password"
                                    value={
                                        this.state.network === 'none'
                                            ? ''
                                            : this.state.network.password
                                    }
                                    onChange={this.passwordDidChange}
                                />
                            </div>
                        </div>
                    </div>
                </div>
            </Disable>
        );
    }

    private isControlKey(
        event: React.KeyboardEvent<HTMLInputElement>
    ): boolean {
        return (
            !!event.key &&
            ['tab', 'delete', 'backspace', 'arrowleft', 'arrowright'].some(
                (key) => event.key.toLocaleLowerCase() === key
            )
        );
    }

    protected noopKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
        if (this.isControlKey(event)) {
            return;
        }
        event.nativeEvent.preventDefault();
        event.nativeEvent.returnValue = false;
    };

    protected numbersOnlyKeyDown = (
        event: React.KeyboardEvent<HTMLInputElement>
    ) => {
        if (this.isControlKey(event)) {
            return;
        }
        const key = Number(event.key);
        if (isNaN(key) || event.key === null || event.key === ' ') {
            event.nativeEvent.preventDefault();
            event.nativeEvent.returnValue = false;
            return;
        }
    };

    protected browseSketchbookDidClick = async () => {
        const uri = await this.props.fileDialogService.showOpenDialog({
            title: 'Select new sketchbook location',
            openLabel: 'Choose',
            canSelectFiles: false,
            canSelectMany: false,
            canSelectFolders: true,
        });
        if (uri) {
            const sketchbookPath = await this.props.fileService.fsPath(uri);
            this.setState({ sketchbookPath });
        }
    };

    protected editAdditionalUrlDidClick = async () => {
        const additionalUrls = await new AdditionalUrlsDialog(
            this.state.additionalUrls,
            this.props.windowService
        ).open();
        if (additionalUrls) {
            this.setState({ additionalUrls });
        }
    };

    protected editorFontSizeDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        const { value } = event.target;
        if (value) {
            this.setState({ editorFontSize: parseInt(value, 10) });
        }
    };

    protected additionalUrlsDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        this.setState({
            additionalUrls: event.target.value
                .split(',')
                .map((url) => url.trim()),
        });
    };

    protected autoScaleInterfaceDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        this.setState({ autoScaleInterface: event.target.checked });
    };

    protected enableLsLogsDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        this.setState({ enableLsLogs: event.target.checked });
    };

    protected interfaceScaleDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        const { value } = event.target;
        const percentage = parseInt(value, 10);
        if (isNaN(percentage)) {
            return;
        }
        const interfaceScale = (percentage - 100) / 20;
        if (!isNaN(interfaceScale)) {
            this.setState({ interfaceScale });
        }
    };

    protected verifyAfterUploadDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        this.setState({ verifyAfterUpload: event.target.checked });
    };

    protected checkForUpdatesDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        this.setState({ checkForUpdates: event.target.checked });
    };

    protected sketchbookShowAllFilesDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        this.setState({ sketchbookShowAllFiles: event.target.checked });
    };

    protected autoSaveDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        this.setState({ autoSave: event.target.checked ? 'on' : 'off' });
    };

    protected quickSuggestionsOtherDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        // need to persist react events through lifecycle https://reactjs.org/docs/events.html#event-pooling
        const newVal = event.target.checked ? true : false;

        this.setState((prevState) => {
            return {
                quickSuggestions: {
                    ...prevState.quickSuggestions,
                    other: newVal,
                },
            };
        });
    };

    protected themeDidChange = (
        event: React.ChangeEvent<HTMLSelectElement>
    ) => {
        const { selectedIndex } = event.target.options;
        const theme = ThemeService.get().getThemes()[selectedIndex];
        if (theme) {
            this.setState({ themeId: theme.id });
        }
    };

    protected compilerWarningsDidChange = (
        event: React.ChangeEvent<HTMLSelectElement>
    ) => {
        const { selectedIndex } = event.target.options;
        const compilerWarnings = CompilerWarningLiterals[selectedIndex];
        if (compilerWarnings) {
            this.setState({ compilerWarnings });
        }
    };

    protected verboseOnCompileDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        this.setState({ verboseOnCompile: event.target.checked });
    };

    protected verboseOnUploadDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        this.setState({ verboseOnUpload: event.target.checked });
    };

    protected sketchpathDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        const sketchbookPath = event.target.value;
        if (sketchbookPath) {
            this.setState({ sketchbookPath });
        }
    };

    protected noProxyDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        if (event.target.checked) {
            this.setState({ network: 'none' });
        } else {
            this.setState({ network: Network.Default() });
        }
    };

    protected manualProxyDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        if (event.target.checked) {
            this.setState({ network: Network.Default() });
        } else {
            this.setState({ network: 'none' });
        }
    };

    protected httpProtocolDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        if (this.state.network !== 'none') {
            const network = this.cloneProxySettings;
            network.protocol = event.target.checked ? 'http' : 'socks';
            this.setState({ network });
        }
    };

    protected socksProtocolDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        if (this.state.network !== 'none') {
            const network = this.cloneProxySettings;
            network.protocol = event.target.checked ? 'socks' : 'http';
            this.setState({ network });
        }
    };

    protected hostnameDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        if (this.state.network !== 'none') {
            const network = this.cloneProxySettings;
            network.hostname = event.target.value;
            this.setState({ network });
        }
    };

    protected portDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        if (this.state.network !== 'none') {
            const network = this.cloneProxySettings;
            network.port = event.target.value;
            this.setState({ network });
        }
    };

    protected usernameDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        if (this.state.network !== 'none') {
            const network = this.cloneProxySettings;
            network.username = event.target.value;
            this.setState({ network });
        }
    };

    protected passwordDidChange = (
        event: React.ChangeEvent<HTMLInputElement>
    ) => {
        if (this.state.network !== 'none') {
            const network = this.cloneProxySettings;
            network.password = event.target.value;
            this.setState({ network });
        }
    };

    private get cloneProxySettings(): ProxySettings {
        const { network } = this.state;
        if (network === 'none') {
            throw new Error('Must be called when proxy is enabled.');
        }
        const copyNetwork = deepClone(network);
        return copyNetwork;
    }
}
export namespace SettingsComponent {
    export interface Props {
        readonly settingsService: SettingsService;
        readonly fileService: FileService;
        readonly fileDialogService: FileDialogService;
        readonly windowService: WindowService;
    }
    export type State = Settings;
}

@injectable()
export class SettingsWidget extends ReactWidget {
    @inject(SettingsService)
    protected readonly settingsService: SettingsService;

    @inject(FileService)
    protected readonly fileService: FileService;

    @inject(FileDialogService)
    protected readonly fileDialogService: FileDialogService;

    @inject(WindowService)
    protected readonly windowService: WindowService;

    protected render(): React.ReactNode {
        return (
            <SettingsComponent
                settingsService={this.settingsService}
                fileService={this.fileService}
                fileDialogService={this.fileDialogService}
                windowService={this.windowService}
            />
        );
    }
}

@injectable()
export class SettingsDialogProps extends DialogProps {}

@injectable()
export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
    @inject(SettingsService)
    protected readonly settingsService: SettingsService;

    @inject(SettingsWidget)
    protected readonly widget: SettingsWidget;

    constructor(
        @inject(SettingsDialogProps)
        protected readonly props: SettingsDialogProps
    ) {
        super(props);
        this.contentNode.classList.add('arduino-settings-dialog');
        this.appendCloseButton('CANCEL');
        this.appendAcceptButton('OK');
    }

    @postConstruct()
    protected init(): void {
        this.toDispose.push(
            this.settingsService.onDidChange(this.validate.bind(this))
        );
    }

    protected async isValid(settings: Promise<Settings>): Promise<DialogError> {
        const result = await this.settingsService.validate(settings);
        if (typeof result === 'string') {
            return result;
        }
        return '';
    }

    get value(): Promise<Settings> {
        return this.settingsService.settings();
    }

    protected onAfterAttach(msg: Message): void {
        if (this.widget.isAttached) {
            Widget.detach(this.widget);
        }
        Widget.attach(this.widget, this.contentNode);
        this.toDisposeOnDetach.push(
            this.settingsService.onDidChange(() => this.update())
        );
        super.onAfterAttach(msg);
        this.update();
    }

    protected onUpdateRequest(msg: Message) {
        super.onUpdateRequest(msg);
        this.widget.update();
    }

    protected onActivateRequest(msg: Message): void {
        super.onActivateRequest(msg);

        // calling settingsService.reset() in order to reload the settings from the preferenceService
        // and update the UI including changes triggerd from the command palette
        this.settingsService.reset();

        this.widget.activate();
    }
}

export class AdditionalUrlsDialog extends AbstractDialog<string[]> {
    protected readonly textArea: HTMLTextAreaElement;

    constructor(urls: string[], windowService: WindowService) {
        super({ title: 'Additional Boards Manager URLs' });

        this.contentNode.classList.add('additional-urls-dialog');

        const description = document.createElement('div');
        description.textContent = 'Enter additional URLs, one for each row';
        description.style.marginBottom = '5px';
        this.contentNode.appendChild(description);

        this.textArea = document.createElement('textarea');
        this.textArea.className = 'theia-input';
        this.textArea.setAttribute('style', 'flex: 0;');
        this.textArea.value = urls
            .filter((url) => url.trim())
            .filter((url) => !!url)
            .join('\n');
        this.textArea.wrap = 'soft';
        this.textArea.cols = 90;
        this.textArea.rows = 5;
        this.contentNode.appendChild(this.textArea);

        const anchor = document.createElement('div');
        anchor.classList.add('link');
        anchor.textContent =
            'Click for a list of unofficial board support URLs';
        anchor.style.marginTop = '5px';
        anchor.style.cursor = 'pointer';
        this.addEventListener(anchor, 'click', () =>
            windowService.openNewWindow(
                'https://github.com/arduino/Arduino/wiki/Unofficial-list-of-3rd-party-boards-support-urls',
                { external: true }
            )
        );
        this.contentNode.appendChild(anchor);

        this.appendAcceptButton('OK');
        this.appendCloseButton('Cancel');
    }

    get value(): string[] {
        return this.textArea.value
            .split('\n')
            .map((url) => url.trim())
            .filter((url) => !!url);
    }

    protected onAfterAttach(message: Message): void {
        super.onAfterAttach(message);
        this.addUpdateListener(this.textArea, 'input');
    }

    protected onActivateRequest(message: Message): void {
        super.onActivateRequest(message);
        this.textArea.focus();
    }

    protected handleEnter(event: KeyboardEvent): boolean | void {
        if (event.target instanceof HTMLInputElement) {
            return super.handleEnter(event);
        }
        return false;
    }
}
